package evq.codec.core.managers.impl;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.Set;

import evq.codec.core.entities.Dictionary;
import evq.codec.core.entities.Vector;
import evq.codec.core.managers.LbgManager;

public class DefaultLBGManager implements LbgManager {

	private static DefaultLBGManager instance;
	private List<VoronoiCell> voronoiCells;

	private DefaultLBGManager(){

		voronoiCells = new ArrayList<VoronoiCell>();
	}
	public static DefaultLBGManager getInstance(){

		if(instance == null){
			instance = new DefaultLBGManager();
		}

		return instance;
	}
	@Override
	public Dictionary createDictionary(List<Vector> trainningSet,
			int dictionarySize) {
		//Creating Initial Dictionary (W0)
		Dictionary initialDictionary = createInitialDictionary(trainningSet, dictionarySize);
		//Setting D-1 to Infinity value
		double oldDistortion = Double.POSITIVE_INFINITY;
		double actualDistortion;
		double actualCriteria;
		double stopCriteria = Math.pow(10, -3);

		//Creating Voronoi Cells
		createVoronoiCells(initialDictionary);

		do{
			//Fill Voronoi Cells and calculate Distortion
			actualDistortion = fillVoronoiCells(trainningSet);
			//Calculate StopCriteria
			actualCriteria = (oldDistortion - actualDistortion)/actualDistortion;
			//Update Old Distortion to actualDistortion.
			oldDistortion = actualDistortion;
			//Get new Centroid on Voronoi Cells
			getNewCentroidOnVoronoiCells();
		}while(actualCriteria > stopCriteria);

		//Get Actual Dictionary
		Dictionary dictionary = getActualDictionary();
		dictionary.setSize(dictionarySize);
		//Set the quantizator dimension
		int quantizatorDimension = trainningSet.get(0).size();
		dictionary.setQuantizatorDimension(quantizatorDimension);

		return dictionary;

	}

	private Dictionary createInitialDictionary(List<Vector> trainningSet, int dictionarySize){

		Random random = new Random();
		Dictionary dictionary = new Dictionary();
		int maxLength = trainningSet.size();

		for(int i = 0; i < dictionarySize; i++){

			int positionNextElement = random.nextInt(maxLength);

			Vector vector = trainningSet.get(positionNextElement);

			dictionary.addVector(i, vector);

		}

		return dictionary;
	}

	private void createVoronoiCells(Dictionary dictionary){


		Set<Integer> keys = dictionary.getCodes();

		for(int key : keys){

			VoronoiCell cell = new VoronoiCell(dictionary.getVector(key));

			voronoiCells.add(cell);
		}
	}

	private void addElementAtVoronoiCell(int voronoiCellPosition, Vector element){

		voronoiCells.get(voronoiCellPosition).addElement(element);
	}

	private double fillVoronoiCells(List<Vector> trainningSet){

		double distortion = 0;
		for(Vector vector : trainningSet){

			double shortestDistance = Double.POSITIVE_INFINITY;
			int cellPosition = 0;

			for(int i = 0 ; i < voronoiCells.size(); i++){

				double distance = calculateDistance(vector, voronoiCells.get(i).getCentroid());

				if(distance < shortestDistance){
					shortestDistance = distance;
					cellPosition = i;
				}
			}
			distortion += shortestDistance;
			addElementAtVoronoiCell(cellPosition, vector);
		}

		return distortion;
	}

	private double calculateDistance(Vector vector, Vector centroid){

		double distance = 0;

		for(int i = 0; i < vector.size(); i++){

			double temp = vector.getElement(i) - centroid.getElement(i);

			distance += Math.pow(temp, 2);
		}

		return distance;
	}

	private void getNewCentroidOnVoronoiCells(){

		for(VoronoiCell cell : voronoiCells){

			cell.setNewCentroid();
		}
	}
	private Dictionary getActualDictionary(){

		Dictionary dictionary = new Dictionary();

		for(int i = 0; i < voronoiCells.size(); i++){

			dictionary.addVector(i, voronoiCells.get(i).getCentroid());
		}

		return dictionary;
	}
	private class VoronoiCell{

		private Vector centroid;

		private List<Vector> elements;

		public VoronoiCell(Vector centroid){

			this.centroid = centroid;
			elements =  new ArrayList<Vector>();
		}

		public void addElement(Vector vector){

			this.elements.add(vector);
		}

		public Vector getCentroid() {
			return centroid;
		}

		public void setNewCentroid(){

			if(elements.size() != 0){
				int dimensionOfVector = elements.get(0).size();

				for(int i = 0; i < dimensionOfVector; i++){

					float valueOfElement = 0;

					for(int j = 0; j < elements.size(); j++){

						valueOfElement += elements.get(j).getElement(i);
					}

					valueOfElement /= elements.size();

					double element = valueOfElement;

					centroid.setElement(i, element);
				}

				removeAllElements();
			}
		}

		private void removeAllElements(){

			this.elements.clear();
		}
	}
}
